#![allow(unused_variables)]

use itertools::Itertools ;
use serde_json::{self, json, Value} ;
use async_trait::async_trait ;

use reqwest::Client ;

use sled::Db ;
// use crate::common::types::Db ;

use crate::minio::types::* ;
use crate::minio::{self, MinioClient} ;

inventory::submit! {
    minio::MinioPlugin {
        new_client: | db: Db, paths: Vec<String>| Box::pin(async {
            log::debug!("checking dataphine minio plugin...") ;
            let path_o = paths.into_iter().find(|x| x.starts_with("dataphine.")) ;
            if path_o.is_none() { return None } ;
            let path = path_o.unwrap() ;
            let id = path.split_once("/").unwrap().0.split_once(".").unwrap().1 ;

            let url = &dotenv::var(&format!("ALIYUN_DATAPHINE_URL__{}", id)).expect("ENV:ALIYUN_DATAPHINE_URL__{id} NOT FIND!") ;
            let ram_username = &dotenv::var(&format!("ALIYUN_DATAPHINE_RAM_USERNAME__{}", id)).expect("ENV:ALIYUN_DATAPHINE_RAM_USERNAME__{id} NOT FIND!") ;
            let ram_password = &dotenv::var(&format!("ALIYUN_DATAPHINE_RAM_PASSWORD__{}", id)).expect("ENV:ALIYUN_DATAPHINE_RAM_PASSWORD__{id} NOT FIND!") ;

            let client = AliyunDataphineClient::new(db, url, ram_username, ram_password).await ;
            match client {
                Ok(client) => {
                    let box_client = Box::new(client) as Box<dyn MinioClient> ;
                    Some(Ok(box_client))
                },
                Err(err) => {
                    Some(Err(err))
                }
            }
        })
    }
}

pub mod consts {
    pub const BOT_TEXT: &str = "BY_LARLUO_BOT" ;
    pub const COOKIE_KEY: &str = "cookie.txt" ;
}
pub mod types {
    use serde::{Deserialize, Serialize};

    #[derive(Deserialize, Serialize)]
    #[serde(rename_all = "camelCase")]
    pub struct ProjectJson {
        pub id: u64,
        pub name: String,
        pub env: String,
    }

    #[derive(Deserialize, Serialize)]
    #[serde(rename_all = "camelCase")]
    pub struct BizDataJson {
        pub id: String,
    }

    #[derive(Deserialize, Serialize)]
    #[serde(rename_all = "camelCase")]
    pub struct PipelineJson {
        pub id: String,
        pub dir_name: String,
        pub name: String,
        pub r#type: String,
        pub count: u64,
        pub biz_data: BizDataJson
    }

    #[derive(Deserialize, Serialize)]
    #[serde(rename_all = "camelCase")]
    pub struct ComputeJson {
        pub id: String,
        pub dir_name: String,
        pub name: String,
        pub r#type: String,
        pub count: u64,
    }

}
use types::* ;

pub struct AliyunDataphineClient {
    db: Db,
    url: String,
    client: Client,
    // client_id: String,
    // ram_username: String,
    // ram_password: String,
    login_profile: Value,
}

impl AliyunDataphineClient {
    pub async fn new(db: Db, url: &str, ram_username: &str, ram_password: &str) -> anyhow::Result<Self> {
        log::debug!("building AliyunDataphineClient ...") ;
        let cookie = db.get(consts::COOKIE_KEY)? ;
        let cookie_store = match cookie.as_ref() {
            Some(cookie) => cookie_store::CookieStore::load_json(cookie.as_ref()).unwrap(),
            None => Default::default()
        } ;
        let cookie_store = reqwest_cookie_store::CookieStoreMutex::new(cookie_store);
        let cookie_store = std::sync::Arc::new(cookie_store);
        let client = reqwest::Client::builder()
            .cookie_provider(std::sync::Arc::clone(&cookie_store))
            .build()? ;

        let user_resp = client.get(&format!("{}/api/userInfo/all", url)).send().await?.json::<Value>().await? ;
        log::debug!("user_resp: {}", user_resp) ;

        if user_resp["code"].as_str() == Some("0") {
            log::info!("reuse cookie SUCCESS!") ;
            let me = AliyunDataphineClient {
                db,
                url: url.to_string(), 
                client,
                login_profile: user_resp,
            };
            return Ok(me) ;
        }	

        log::info!("call http to acquire cookie...") ;
        let form = std::collections::HashMap::from([
            ("RequestContext",
             json!({"RequestId":""}).to_string()),
            ("RequestParams",
             json!({"LoginName": ram_username,
                    "Password": ram_password
             }).to_string())
        ]) ;
        let login_resp = client.post("https://signin.aliyun.com/entrance?Action=V2Login").form(&form).send().await?.json::<Value>().await? ;
        log::debug!("login_resp: {}", login_resp) ;
        if login_resp["ResultCode"] == "failure" {
            return Err(anyhow::anyhow!("login failed: {}", login_resp)) ;
        }
        if !login_resp["rgv587_flag"].is_null() {
            return Err(anyhow::anyhow!("ui auth needed: {}", login_resp)) ;
        }
        let token = login_resp["ResultData"]["ActionToken"].as_str().unwrap() ;
        let session_resp = client.get(format!("https://signin.aliyun.com/session.htm?token={}", token)).send().await?.text().await? ;
        log::debug!("session_resp: {}", login_resp) ;

        let index_resp = client.get(url).send().await?.text().await? ;        
        log::debug!("index_resp: {}", index_resp) ;
        let user_resp = client.get(&format!("{}/api/userInfo/all", url)).send().await?.json().await? ;
        log::debug!("user_resp: {}", user_resp) ;

        let cookie_store = cookie_store.lock().unwrap();
        let mut cookie_buffer = Vec::<u8>::new() ;
        cookie_store.save_json(&mut cookie_buffer).unwrap() ;

        let cookie_store_json = serde_json::to_string(&*cookie_store) ;

        db.insert(consts::COOKIE_KEY, cookie_buffer)? ;
        db.flush_async().await? ;

        let me = AliyunDataphineClient {
            db,
            url: url.to_string(), 
            client,
            login_profile: user_resp,
        };
        Ok(me)
    }
}

#[async_trait]
impl MinioClient for AliyunDataphineClient {
    async fn mb(&self, req: MbReq) -> anyhow::Result<Value> {
        Ok(json!("unimplemented"))
    }
    async fn ls(&self, req: LsReq) -> anyhow::Result<Value> {
        log::debug!("dataphine-ls: {:?}", req) ;
        let path_parts_vec = req.path.split('/').collect_vec() ;
        let projects = self.login_profile["data"]["tenantProjects"].clone();

        let project_json_vec = serde_json::from_value::<Vec<ProjectJson>>(projects)? ;
        if path_parts_vec.get(1).is_none() || path_parts_vec.get(1) == Some(&"") {
            let display = project_json_vec.into_iter().map(|it| it.name).collect_vec() ;
            return Ok(serde_json::to_value(display).unwrap()) ;
        }

        if path_parts_vec.get(2).is_none() || path_parts_vec.get(2) == Some(&"") {
            return Ok(json!(vec!["离线管道", "计算任务"]))
        }

        let project_name = path_parts_vec.get(1).unwrap().clone() ;
        let project_json = project_json_vec.iter().find(|it| it.name == project_name).ok_or_else(|| anyhow::anyhow!("PROJECT NOT FIND!"))? ;

        let module_name = path_parts_vec.get(2).unwrap().to_string() ;
        let module_path = format!("/{}", if path_parts_vec.get(3).is_some() { path_parts_vec[3..].join("/") } else { "".to_string() } ) ;
        let module_path = if module_path.ends_with("/") { module_path } else { format!("{}/", module_path) }  ;

        match &module_name[..] {
            "离线管道" => {
                let url = format!("{}/api/pipeline/tree/offlinePipeline?type=offlinePipeline&projectId={}&directory={}&env={}", 
                                  self.url, project_json.id, module_path, project_json.env) ;
                log::debug!("list-req: {}", url) ;
                let mut resp = self.client.get(url).send().await?.json::<Value>().await?;
                log::debug!("list-resp: {}", resp) ;

                let display = serde_json::from_value::<Vec<PipelineJson>>(resp["data"]["children"].take()).unwrap() ;
                let display = display.into_iter().map(|it| {
                    format!("{}{}", &it.name, if it.r#type=="directory" {"/"} else {""} )
                }).collect_vec() ;
                Ok(serde_json::to_value(display).unwrap())
            },
            "计算任务" => {
                let url = format!("{}/api/tree/compute/task?type=dataManage&projectId={}&directory={}&env={}", self.url, project_json.id, module_path, project_json.env) ;
                log::debug!("list-req: {}", url) ;
                let mut resp = self.client.get(url).send().await?.json::<Value>().await?;
                log::debug!("list-resp: {}", resp) ;

                let display = serde_json::from_value::<Vec<ComputeJson>>(resp["data"]["children"].take()).unwrap() ;
                let display = display.into_iter().map(|it| {
                    format!("{}{}", &it.name, if it.r#type=="directory" {"/"} else {""} )
                }).collect_vec() ;
                Ok(serde_json::to_value(display).unwrap())
            },
            _ => unimplemented!()
        }
    }
    async fn cp(&self, req: CpReq) -> anyhow::Result<Value> {
        unimplemented!()
    }
    async fn cat(&self, req: CatReq) -> anyhow::Result<Value> {
        unimplemented!()
    }
    async fn tee(&self, req: TeeReq) -> anyhow::Result<Value> {
        unimplemented!()
    }
    async fn rm(&self, req: RmReq) -> anyhow::Result<Value> {
        let path_parts_vec = req.paths[0].split('/').collect_vec() ;
        let projects = self.login_profile["data"]["tenantProjects"].clone();

        let project_json_vec = serde_json::from_value::<Vec<ProjectJson>>(projects)? ;
        if path_parts_vec.get(1).is_none() || path_parts_vec.get(1) == Some(&"") {
            return Err(anyhow::anyhow!("PROJECT NAME IS MISSING!")) ;
        }
        if path_parts_vec.get(2).is_none() || path_parts_vec.get(2) == Some(&"") {
            return Err(anyhow::anyhow!("MODULE NAME IS MISSING!")) ;
        }

        let project_name = path_parts_vec.get(1).unwrap().clone() ;
        let project_json = project_json_vec.iter().find(|it| it.name == project_name).ok_or_else(|| anyhow::anyhow!("PROJECT NOT FOUND!"))? ;

        let module_name = path_parts_vec.get(2).unwrap().to_string() ;
        let module_path = format!("/{}", if path_parts_vec.get(3).is_some() { path_parts_vec[3..].join("/") } else { "".to_string() } ) ;
        let (leaf_dir, leaf_name) = module_path.split_once('/').unwrap() ;

        match module_name.as_str() {
            "离线管道" => {
                let url = format!("{}/api/pipeline/tree/offlinePipeline?type=offlinePipeline&projectId={}&directory={}&env={}", 
                                  self.url, project_json.id, leaf_dir, project_json.env) ;
                let mut list_resp = self.client.get(url).send().await?.json::<Value>().await?;
                log::debug!("list-resp: {}", list_resp) ;

                let pipeline_json = serde_json::from_value::<Vec<PipelineJson>>(list_resp["data"]["children"].take()).unwrap()
                    .into_iter().find(|it| it.name == leaf_name).ok_or_else(|| anyhow::anyhow!("FILE NOT FOUND!"))? ;
                let url = format!("{}/api/pipeline/node/{}/category/18/nodes/{}?id={}&deleteComment={}&env={}", 
                                  &self.url, project_json.id, pipeline_json.id, pipeline_json.biz_data.id, consts::BOT_TEXT, project_json.env) ;
                let delete_resp = self.client.delete(url).send().await?.json::<Value>().await? ;
                log::debug!("delete-resp: {}", delete_resp) ;

                Ok(delete_resp)
            },
            "计算任务" => {
                let url = format!("{}/aip/tree/compute/task?type=dataManage&projectId={}&directory={}&env={}", self.url, project_json.id, leaf_dir, project_json.env) ;
                let mut list_resp = self.client.get(url).send().await?.json::<Value>().await?;
                log::debug!("list_resp: {}", list_resp) ;

                let compute_json = serde_json::from_value::<Vec<ComputeJson>>(list_resp["data"]["children"].take()).unwrap()
                    .into_iter().find(|it| it.name == leaf_name).ok_or_else(|| anyhow::anyhow!("FILE NOT FOUND!"))? ;
                let url = format!("{}/api/dataProcess/{}/nodes/{}?file_name={}&deleteComment={}&env={}", 
                                  &self.url, project_json.id, compute_json.id, leaf_name, consts::BOT_TEXT, project_json.env) ;
                let delete_resp = self.client.delete(url).send().await?.json::<Value>().await? ;
                log::debug!("delete-resp: {}", delete_resp) ;

                Ok(delete_resp)
            },
            _ => unimplemented!()
        }
    }
    async fn share(&self, req: ShareReq) -> anyhow::Result<Value> {
        unimplemented!()
    }
    async fn mirror(&self, req: MirrorReq) -> anyhow::Result<Value> {
        unimplemented!()
    }
    async fn sql(&self, req: SqlReq) -> anyhow::Result<Value> {
        unimplemented!()
    }
}
